#include "Wt.h"

/* Initialize the server */

void WtInitServer(server_rec *s, pool *p)
{
  ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s,
    "Wtcl: WtInitServer. Using %d threads per child.", n);

  Tcl_FindExecutable(ap_server_argv0);
  /* Tcl_FindExecutable("C:\\Program Files\\Tcl\\bin\\tclsh84.exe"); */

  /* Custom types */

  Tcl_RegisterObjType(&WtTableType);
  Tcl_RegisterObjType(&WtClnType);
}

/* Get the current context */

WtContext *WtGetContext()
{
  WtThreadStorage *ts = WtGetThreadStorage();
  if (!ts->context) {
    ts->context = calloc(sizeof(WtContext));
    WtInitContext(ts->context);
  }
  return ts->context;
}

/* Initialize the context */

void WtInitContext(WtContext *w)
{
  context->persistent.server.isPersistent = 1;

  /* Create the web output channel */

  w->webChannel = Tcl_CreateChannel(&WtOutChanType, "web", w, TCL_WRITABLE);
  Tcl_SetStdChannel(w->webChannel, TCL_STDOUT);

  WtInitPtrRegistry(w);
}

/* Get the current thread storage */

WtThreadStorage *WtGetThreadStorage()
{
  WtThreadStorage *p = (WtThreadStorage *)TlsGetValue(tls_index);
  if (!p) {
    p = (WtThreadStorage *)calloc(1, sizeof(WtThreadStorage));
    p->context = NULL;
    TlsSetValue(tls_index, p);
  }
  return p;
}

/* Process a request */

int WtProcessRequest(request_rec *r)
{
  WtContext *w;
  int ok, status;

  ap_soft_timeout("Wtcl request", r);

  /* Get the context */

  w = WtGetContext();

  /* Initialize the request */

  ok = WtInitRequest(w, r);

  /* Run the request */

  if (ok) {
    if (r->finfo.st_mode == 0) {
      WtApLog(APLOG_MARK, APLOG_ERR, w,
              "Wtcl: File not found: \"%s\".", r->filename);
      w->active->response.status = HTTP_NOT_FOUND;
    } else {
      ap_chdir_file(r->filename);
      ok = WtExecuteRequest(w, r->filename);
    }
  }

  /* End the request */

  WtEndRequest(w);

  status = w->active->response.status;
  w->active->response.status = OK;

  ap_kill_timeout(r);

  return status;
}

/* Initialize the request */

int WtInitRequest(WtContext *w, request_rec *r)
{
  Tcl_Obj *settings = WtNewTableObj(NULL), *inheritedSettings, *pkg;
  Tcl_Obj *serverConf, *dirConf;
  const char *pkgName = NULL, *pkgVer = NULL;
  int initPackage = 0, reuseInterp = 0, pkgExactVer = 0;

  /* Build the config */

  WtClearTable(settings);
  if (serverConf = WtGetServerConf(r->server)) {
    WtUpdateTable(settings, serverConf);
  }
  if (dirConf = WtGetDirConf(r)) {
    WtUpdateTable(settings, dirConf)
  }
  WtUpdateTable(settings, w->defaultSettings);

  /* Get the web context */

  reuseInterp = WtTableGetBoolFromStr(settings, "reuseInterp", 1);

  if (reuseInterp) {
    w->active = &w->persistent;
  } else {
    w->active = ap_pcalloc(r->pool, sizeof(WtWebContext));
    w->active->server.recycle = 1;
  }

  /* Initialize the interpreter */

  if (!w->active->server.interp) {
    ok = WtInitInterp(w, s, r, settings);
    if (ok) {
      ok = WtInitWebContext(w, settings);
    }
    initPackage = 1;
  } else {
    w->active->server.apServer = s;
    w->active->request.apReq = r;
  }

  /* Reset the request */

  w->active->response.status = OK;

  WtUpdateTable(w->active->server.inheritedSettings, settings);

  /* Initialize the package */

  if (initPackage) {
    if (ok) {
      if (WtTableStrExists(settings, "package")) {
        pkg = WtTableGetObjFromStr(settings, "package", NULL);
        pkgName = WtTableGetStrFromStr(pkg, "name", NULL);
        pkgVer = WtTableGetStrFromStr(pkg, "version", NULL);
        pkgExactVer = WtTableGetBoolFromStr(pkg, "exact", 0);
      }

      if (!pkgName) {
        pkgName = "wt";
        pkgVer = NULL;
        pkgExactVer = 0;
      }

      if (pkgName && *pkgName &&
          Tcl_PkgRequire(interp, pkgName, pkgVer, pkgExactVer) == NULL) {
        WtApLog(APLOG_MARK, APLOG_ERR, w,
                "Wtcl: Failed to activate package \"%s\": %s"
                pkgName, Tcl_GetStringResult(interp), NULL);
        w->active->server.interpErrorState = 1;
        ok = 0;
      }
    }

    if (ok) {
      ok = WtInitRequestSettings(w, settings, interp);
      if (!ok) {
        w->active->server.interpErrorState = 1;
      }
    }

    if (ok) {
      if (Tcl_GetCharLength(w->active->server.interpCreateScript) &&
          Tcl_EvalObj(interp, w->active->server.interpCreateScript) != TCL_OK) {
        WtApLog(APLOG_MARK, APLOG_ERR, w,
          "Wtcl: Failed to evaluate interp create script: %s",
          Tcl_GetStringResult(interp));
        w->active->server.interpErrorState = 1;
        ok = 0;
      }
    }
    w->active->server.processedInterpCreateScript = 1;
  }

  /* If the interpreter failed to initialize, recycle it */

  if (!ok) {
    w->active->server.recycle = 1;
  }

  /* Run the request start script */

  if (ok) {
    if (Tcl_GetCharLength(w->active->request.startScript) &&
        Tcl_EvalObj(interp, w->active->request.startScript) != TCL_OK) {
        WtApLog(APLOG_MARK, APLOG_ERR, w,
          "Wtcl: Failed to evaluate request start script: %s",
          Tcl_GetStringResult(interp));
        w->active->server.interpErrorState = 1;
        ok = 0;
      }
    }
    w->active->request.processedStartScript = 1;
  }

  /* If we encountered an error, set the response code */

  if (!ok) {
    w->active->response.status == HTTP_INTERNAL_SERVER_ERROR;
  }

  return ok;
}

/* Create the interpreter */

int WtInitInterp(WtContext *w, server_rec *s, request_rec *r, Tcl_Obj *settings)
{
  int ok = 1, wtPkgExactVer = 0;
  Tcl_Interp *interp;
  Tcl_Obj *settings, *pkg;
  const char *wtPkgName = NULL, *wtPkgVer = NULL, *createScript;

  w->active->server.apServer = s;
  w->active->server.apReq = r;

  WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w, "Wtcl: WtInitInterp.");

  interp = w->active->server.interp = Tcl_CreateInterp();

  if (!interp) {
    WtApLog(APLOG_MARK, APLOG_ERR, w, "Wtcl: Tcl_CreateInterp failed.");
    ok = 0;
  } else {
    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: Created interp (0x%x).", interp);
  }

  if (ok) {
    WtApLog(APLOG_MARK, APLOG_ERR, w, "Wtcl: Calling Tcl_Init.");
    if (Tcl_Init(interp) != TCL_OK) {
      WtApLog(APLOG_MARK, APLOG_ERR, w, "Wtcl: Tcl_Init failed: %s",
              Tcl_GetStringResult(interp));
      ok = 0;
    } else {
      WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
              "Wtcl: Tcl_Init succeeded.");
    }
  }

  if (!ok && interp) {
    Tcl_DeleteInterp(interp);
    interp = w->active->server.interp = NULL;
  }

  return ok;
}

/* Initialize the web context */

int WtInitWebContext(WtContext *w, Tcl_Obj *settings)
{
  int ok = 1;
  Tcl_Interp *interp;

  interp = w->active->server.interp;
  Tcl_SetAssocData(interp, "wt::context", NULL, w);

  Tcl_RegisterChannel(interp, w->webChannel);

  /* Namespaces */

  Tcl_Obj *init = WtNewString("");
  Tcl_AppendToObj(init, "namespace eval ::wt:: {}\n", -1);
  Tcl_AppendToObj(init, "namespace eval ::wt::server:: {}\n", -1);
  Tcl_AppendToObj(init, "namespace eval ::wt::request:: {}\n", -1);
  Tcl_AppendToObj(init, "namespace eval ::wt::response:: {}\n", -1);
  Tcl_AppendToObj(init, "namespace eval ::wt::session:: {}\n", -1);
  if (Tcl_Eval(interp, init) != TCL_OK) {
    ok = 0;
    return ok;
  }

  /* Tables */

  WtInitTableCommands(interp);

  /* Settings */

  w->active->server.inheritedSettings = WtNewTableObj("wt::defaultSettings");
  WtCreateTableCmd(interp, "wt::defaultSettings", w->defaultSettings);

  w->active->server.inheritedSettings = WtNewTableObj("wt::inheritedSettings");
  WtCreateTableCmd(interp, "wt::inheritedSettings", w->active->server.inheritedSettings);

  /* Includes */

  w->active->server.includeStartScripts = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(w->active->server.includeStartScripts);

  w->active->server.includeEndScripts = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(w->active->server.includeEndScript);

  w->active->server.includeNamespace = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.includeNamespace);

  w->active->server.addHeaderTrailer = 1;

  w->active->server.includeHeader = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.includeHeader);

  w->active->server.includeTrailer = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.includeTrailer);

  w->active->server.scriptStartString = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.scriptStartString);

  w->active->server.scriptEndString = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.scriptEndString);

  w->active->server.evalText = 1;

  w->active->server.scriptStack = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(w->active->server.scriptStack);

  w->active->server.lastIncludeError = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.lastIncludeError);

  w->active->server.errorHandler = Tcl_NewStringObj(NULL, 0);
  Tcl_IncrRefCount(w->active->server.errorHandler);

  /* Server environment variables */

  w->active->request.serverVars = WtNewTableObj("wt::serverVars");
  Tcl_IncrRefCount(w->active->request.serverVars);

  /* Request headers */

  w->active->request.headers = WtNewStringTableObj("wt::requestHeaders");
  Tcl_IncrRefCount(w->active->request.headers);

  /* Request variables */

  w->active->request.queryParams = WtNewTableObj("wt::queryParams");
  Tcl_IncrRefCount(w->active->request.queryParams);

  w->active->request.postParams = WtNewTableObj("wt::postParams");
  Tcl_IncrRefCount(w->active->request.postParams);

  w->active->request.params = WtNewTableObj("wt::params");
  Tcl_IncrRefCount(w->active->request.params);

  /* Request cookies */

  w->active->request.cookies = WtNewTableObj("wt::requestCookies");
  Tcl_IncrRefCount(w->active->request.cookies);

  /* File uploads */

  w->active->request.uploads = WtNewTableObj("wt::uploads");
  Tcl_IncrRefCount(w->active->request.uploads);

  w->active->request.uploadHandler = Tcl_NewStringObj("", 0);
  Tcl_IncrRefCount(w->active->request.uploadHandler);

  w->active->request.uploadHandlerData = NULL;

  WtInitUploadCommands(interp);

  /* Response headers */

  w->active->response.headers = WtNewStringTableObj("wt::responseHeaders");
  Tcl_IncrRefCount(w->active->response.headers);

  w->active->response.successHeaders = WtNewStringTableObj("wt::errorHeaders");
  Tcl_IncrRefCount(w->active->response.successHeaders);

  /* Response cookies */

  w->active->response.cookies = WtNewCookieClnObj("wt::responseCookies");
  Tcl_IncrRefCount(w->active->response.cookies);

  WtInitCookieCommands(interp);

  /* Basic commands */

  WtInitBasicCommands(interp);

  return rc == TCL_OK;
}

/* Have the headers been flushed */

int WtHeadersSent(WtContext *w)
{
  return w->active->response.headersSent;
} 

/* Flush the headers */

int WtSendHeaders(WtContext *w)
{
  char *ctype;

  if (WtHeadersSent(w)) {
    return 1;
  }

  /* Get content type */

  ctype = WtTableGetStrFromStr(w->active->response.headers, "Content-Type", NULL);

  if (ctype) {
    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: Content type was set: \"", ctype, "\".");
    w->active->request.apReq->content_type = ap_pstrdup(w->active->request.apReq->pool, ctype);
  } else {
    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: No content type was set.");
  }

  if (!w->active->request.apReq->content_type) {
    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: Setting default content type.");
    w->active->request.apReq->content_type = "text/html";
  }

  /* Fill apache tables */

  WtUpdateApTableFromTable(w->active->request.apReq->err_headers_out, w->active->response.headers);
  WtUpdateApTableFromTable(w->active->request.apReq->headers_out, w->active->response.successHeaders);
  WtLoadResponseCookies(w);

  /* Write headers */

  WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
    "Wtcl: Sending headers.");

  ap_send_http_header(w->active->request.apReq);

  w->active->response.headersSent = 1;

  return 1;
}

/* End the request */

int WtEndRequest(WtContext *w)
{
  int ok = 1;

  /* Run the exit handlers */

  if (w->active->server.interp) {

    /* Run the end request handler */

    if (w->active->request.processedStartScript &&
        Tcl_GetCharLength(w->active->request.endScript) &&
        Tcl_EvalObj(interp, w->active->request.endScript) != TCL_OK) {
      WtApLog(APLOG_MARK, APLOG_ERR, w, "Wtcl: Failed to evaluate request end script: %s",
        Tcl_GetStringResult(interp));
      w->active->server.interpErrorState = 1;
      ok = 0;
    }

    /* Run the interp delete handler */

    if (w->active->server->recycle &&
        w->active->server.processedInterpCreateScript = 1 &&
        Tcl_GetCharLength(w->active->server.interpDeleteScript) &&
        Tcl_EvalObj(interp, w->active->server.interpDeleteScript) != TCL_OK) {
      WtApLog(APLOG_MARK, APLOG_ERR, w, "Wtcl: Failed to evaluate request end script: %s",
        Tcl_GetStringResult(interp));
      w->active->server.interpErrorState = 1;
      ok = 0;
    }

  }

  /* Process unhandled errors */

  if (w->active->server.interpErrorState) {
    ok = WtHandleInterpError(w);
    w->active->server.interpErrorState = 0;
  }

  /* If we encountered an error, set the response code */

  if (!ok) {
    w->active->response.status == HTTP_INTERNAL_SERVER_ERROR;
  }

  /* Flush the response */

  Tcl_Flush(w->webChannel);

  if (!WtHeadersSent(w)) {
    WtSendHeaders(w);
  }

  if (w->active->response.status == HTTP_INTERNAL_SERVER_ERROR) {
    w->active->request.apReq->assbackwards = 1;
  }

  /* Cleanup */

  if (w->active->server.recycle) {
    if (w->active->server.interp) {
      if (!Tcl_InterpDeleted(w->active->server.interp)) {
        Tcl_DeleteInterp(w->active->server.interp);
      }
      w->active->server.interp = NULL;
    }
  }

  w->active.server.apServer = NULL;
  WtClearTable(w->active->server.inheritedSettings);
  Tcl_SetListObj(w->active->server.scriptStack, 0, NULL);

  w->active->request.apReq = NULL;
  w->active->request.apWeb = NULL;
  w->active->request.apCookies = NULL;
  w->active->request.envInitialized = 0;
  WtClearTable(w->active->request.serverVars);
  WtClearTable(w->active->request.headers);
  w->active->request.inputInitialized = 0;
  WtClearTable(w->active->request.queryParams);
  WtClearTable(w->active->request.postParams);
  WtClearTable(w->active->request.params);
  WtClearTable(w->active->request.cookies);
  WtClearTable(w->active->request.uploads);
  Tcl_SetStringObj(w->active->request.uploadHandler, NULL, 0);
  if (w->active->request.uploadHandlerData) {
    Tcl_DecrRefCount(w->active->request.uploadHandlerData);
    w->active->request.uploadHandlerData = NULL;
  }

  WtClearCollection(w->active->response.cookies);
  WtClearTable(w->active->response.headers);
  WtClearTable(w->active->response.successHeaders);
  w->active->response.headersSent = 0;

  return ok;
}

void WtEndServer(server_rec *s, pool *p)
{
  ;
}

void WtDeleteContext(WtContext *w)
{
  WtDeletePtrRegistry(w);
  Tcl_DecrRefCount(w->defaultSettings);

  if (w->active != &w->persistent) {
    WtDeleteInterpContext(w->active);
  }

  WtDeleteInterpContext(&w->persistent);
}

void WtDeleteInterpContext(WtInterpContext *i)
{
  Tcl_Interp *interp;

  if (i->server.interp) {
    interp = i->server.interp;
    if (interp) {
      if (!Tcl_InterpDeleted(interp)) {
        Tcl_DeleteInterp(interp);
      }
      i->server.interp = NULL;
    }
  }

  Tcl_DecrRefCount(i->server.inheritedSettings);
  Tcl_DecrRefCount(i->server.scriptStack);
  Tcl_DecrRefCount(i->serverVars);
  Tcl_DecrRefCount(i->request.headers);
  Tcl_DecrRefCount(i->request.queryParams);
  Tcl_DecrRefCount(i->request.postParams);
  Tcl_DecrRefCount(i->request.params);
  Tcl_DecrRefCount(i->request.cookies);
  Tcl_DecrRefCount(i->request.uploads);
  Tcl_DecrRefCount(i->request.uploadHandler);
  if (i->request.uploadHandlerData) {
    Tcl_DecrRefCount(i->request.uploadHandlerData);
  }
  Tcl_DecrRefCount(i->response.cookies);
  Tcl_DecrRefCount(i->response.headers);
  Tcl_DecrRefCount(i->response.successHeaders);
}

/* Initialize commands that use the request */

int WtCmdInitRequest(Tcl_Interp *interp)
{
  WtContext *w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);
  return WtReadRequest(w, 1);
}

/* Read the request data */

int WtReadRequest(WtContext *w, int setError)
{
  int ret = 1;
  const char *errMsg;
  table *tbl;

  if (!w->active->request.inputInitialized) {

    WtUpdateTableFromApTable(w->active->request.headers, w->active->request.apReq->headers_in);

    w->active->request.apWeb = ApacheRequest_new(w->active->request.apReq);

    WtLoadRequestCookies(w);

    if (ApacheRequest_parse(w->active->request.apWeb) != OK) {
      if (setError) {
        errMsg = ap_table_get(w->active->request.apReq->notes, "error-notes");
        if (errMsg && *errMsg) {
          Tcl_AppendStringsToObj(Tcl_GetObjResult(w->active->server.interpCtx->interp),
            "Failed to read request data: ", errMsg, NULL);
        } else {
          Tcl_AppendStringsToObj(Tcl_GetObjResult(w->active->server.interpCtx->interp),
            "Failed to read request data.", NULL);
        }
      }
      ret = 0;
    } else {
      tbl = ApacheRequest_query_params(w->active->request.apWeb, w->active->request.apReq->pool);
      WtUpdateTableFromApTable(w->active->request.queryParams, tbl);

      tbl = ApacheRequest_post_params(w->active->request.apWeb, w->active->request.apReq->pool);
      WtUpdateTableFromApTable(w->active->request.postParams, tbl);

      WtUpdateTable(w->active->request.params, w->active->request.cookies);
      WtUpdateTable(w->active->request.params, w->active->request.queryParams);
      WtUpdateTable(w->active->request.params, w->active->request.postParams);

      WtLoadRequestFiles(w);
    }

    w->active->request.inputInitialized = 1;
  }

  return ret;
}

void WtLoadRequestCookies(WtContext *w)
{
  int count, i, nvals, j;
  ApacheCookie *cookie;
  char *name, *val;

  w->active->request.apCookies = ApacheCookie_parse(w->active->request.apReq, NULL);

  if (!w->active->request.apCookies) {
    return;
  }

  count = ApacheCookieJarItems(w->active->request.apCookies);

  for (i = 0; i < count; i++) {
    cookie = ApacheCookieJarFetch(w->active->request.apCookies, i);
    if (cookie) {
      name = cookie->name ? cookie->name : "";
      nvals = ApacheCookieItems(cookie);
      for (j = 0; j < nvals; j++) {
        val = ApacheCookieFetch(cookie, j);
        if (!val) val = "";
        WtTableAddStrToStr(w->active->request.cookies, name, val);
      }
    }
  }
}

void WtLoadRequestFiles(WtContext *w)
{
  ApacheUpload *upload;
  Tcl_Obj *uploadObj;
  Tcl_Obj *key;

  upload = ApacheRequest_upload(w->active->request.apWeb);
  for (; upload; upload = upload->next) {
    uploadObj = WtNewUploadObj(upload);
    key = WtNewString(upload->name ? upload->name : "");
    WtTableAdd(w->active->request.uploads, key, uploadObj);
  }
}

void WtInitResponse(WtContext *w)
{
  ;
}

void WtLoadResponseCookies(WtContext *w)
{
  int i, j, len, secure, err;
  Tcl_Obj *cookieObj, *valObj;
  WtCookie *cookie;
  ApacheCookie *apCookie;
  char *value;

  for (i = 0; i < WtClnSize(w->active->response.cookies); i++) {
    err = 0;

    cookieObj = WtGetClnItemAtIndex(w->active->response.cookies, i);
    cookie = WtCookieRep(cookieObj);
    apCookie = ApacheCookie_new(w->active->request.apReq);
    apCookie->name = ap_pstrdup(w->active->request.apReq->pool, WtSafeStr(WtToString(cookie->name)));
    apCookie->expires = ap_pstrdup(w->active->request.apReq->pool, WtSafeStr(WtToString(cookie->expires)));
    apCookie->domain = ap_pstrdup(w->active->request.apReq->pool, WtSafeStr(WtToString(cookie->domain)));
    apCookie->path = ap_pstrdup(w->active->request.apReq->pool, WtSafeStr(WtToString(cookie->path)));

    if (Tcl_GetBooleanFromObj(NULL, cookie->secure, &secure) != TCL_OK) {
      err = 1;
    } else {
      apCookie->secure = secure;
    }

    len = WtGetListSize(cookie->values);
    for (j = 0; j < len; j++) {
      valObj = WtGetListItem(cookie->values, j);
      value = WtSafeStr(WtToString(valObj));
      ApacheCookieAdd(apCookie, value);
    }

    if (!err) {
      ApacheCookie_bake(apCookie);
    }
  }
}

Tcl_Obj *WtGetServerConf(server_rec *s)
{
  return (Tcl_Obj *)ap_get_module_config(s->module_config, &wtcl_module);
}

Tcl_Obj *WtGetDirConf(request_rec *r)
{
  return (Tcl_Obj *)ap_get_module_config(r->per_dir_config, &wtcl_module);
}

WtContext *WtGetAssocContext(Tcl_Interp *interp)
{
  return (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);  
}

/*
 * Web output channel
 */

static int WtOutBlockModeProc(ClientData instanceData, int mode)
{
  return 0;
}

static int WtOutCloseProc(ClientData instanceData, Tcl_Interp *interp)
{
  /* Free channel data */
  return 0;
}

static int WtOutInputProc(ClientData instanceData, char *buf, int bufSize, int *errorCodePtr)
{
  /* Not implemented */
  *errorCodePtr = EINVAL;
  return -1;
}

static int WtOutOutputProc(ClientData instanceData, const char *buf, int toWrite, int *errorCodePtr)
{
  WtContext *w = (WtContext *)instanceData;
  if (WtHeadersSent(w) || WtSendHeaders(w)) {
    ap_rwrite(buf, toWrite, w->active->request.apReq);
    return toWrite;
  } else {
    *errorCodePtr = EINVAL;
  }
  return -1;
}

static int WtOutSetOptionProc(ClientData instanceData, Tcl_Interp *interp, const char *optionName, const char *newValue)
{
  return Tcl_BadChannelOption(interp, optionName, NULL);
}

static void WtOutWatchProc(ClientData instanceData, int mask)
{
  /* Select on output handle */
}

static int WtOutGetHandleProc(ClientData instanceData, int direction, ClientData *handlePtr)
{
  /* Not implemented */
  return TCL_ERROR;
}

Tcl_ChannelType WtOutChanType = {
  "web",
  TCL_CHANNEL_VERSION_2,
  WtOutCloseProc,
  WtOutInputProc,
  WtOutOutputProc,
  NULL,
  WtOutSetOptionProc,
  NULL,
  WtOutWatchProc,
  WtOutGetHandleProc,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};
